/**
 * @fileOverview
 * @imports org.puremvc.js.patterns.observer.Observer
 * @imports org.puremvc.js.interfaces.IView
 * @imports org.puremvc.js.interfaces.INotification
 * @imports org.puremvc.js.interfaces.IMediator
 * @exports View as org.puremvc.js.core.View
 */

/**
 * @constructor
 */
function View ()
{
	if (null != View.instance)
	{
		throw new Error (View.SINGLETON_MSG);
	}
	
	View.instance= this;
	this.mediatorMap= {};
	this.observerMap= {};
	this.initializeView();
}

/**
 * @protected
 * @return {void}
 */
View.prototype.initializeView= function () {};

/**
 * @return {View}
 */
View.getInstance= function ()
{
	if (View.instance == null)
	{
		View.instance= new View;
	}
	
	return View.instance;
};

/**
 * @param {string} notificationName
 * @param {Observer} observer
 * @return {void}
 */
View.prototype.registerObserver= function (notificationName, observer)
{
	var observers= this.observerMap[notificationName];
	if (observers)
	{
		observers.push(observer);
	}
	else
	{
		this.observerMap[notificationName]= [observer]
	};
};

/**
 * @param {Notification} notification
 * @return {void}
 */
View.prototype.notifyObservers= function (notification)
{
	var observers= this.observerMap[notification.getName()]
	if (null != observers)
	{
		// use a copy of the observers list to guarantee
		// no modifications during dispatch of notifications
		observers= observers.concat();
		for (var i= 0, n= observers.length, observer; i < n; i++)
		{
			//console.info('sending notification: ' + notification);
			observers[i].notifyObserver(notification);
		}
	}
};

/**
 * @param {string} notificationName
 * @param {Object} notifyContext
 * @return {void}
 */
View.prototype.removeObserver= function (notificationName, notifyContext)
{
	var observers= this.observerMap[notificationName];
	
	// what if there is no observers for this notificationName ?
	// In the original AS3 implementation, there is no null check.
	// However, we deviate from the existing AS3 implementation to
	// ensure that the observer list does in fact exist. If it doesn't
	// we return early
	
	if (null == observers)
	{
		return;
	}

	for (var i= 0, n= observers.length; i < n; i++)
	{
		if (observers[i].compareNotifyContext(notifyContext))
		{
			observers.splice(i, 1);
			break;
		}
	}
	
	if (0 === observers.length)
	{
		delete this.observerMap[notificationName];
	}
};

/**
 * 
 * @param {Mediator} mediator 
 * @return {void}
 */
View.prototype.registerMediator= function (mediator)
{
	if (this.mediatorMap[mediator.getMediatorName()] != null)
	{
		return;
	}
	
	this.mediatorMap[mediator.getMediatorName()]= mediator;
	
	var interests= mediator.listNotificationInterests()
	,	observer;
	if (interests.length)
	{
		observer= new Observer(mediator.handleNotification, mediator);
		for (var i= 0, n= interests.length; i < n; i++)
		{
			this.registerObserver(interests[i], observer);
		}
	}
	
	mediator.onRegister();
};

/**
 * @param {string} mediatorName
 * @return {Mediator|null} 
 */
View.prototype.retrieveMediator= function (mediatorName)
{
	return this.mediatorMap[mediatorName];
};

/**
 * @param {string} mediatorName
 * @return {Mediator|null}
 */
View.prototype.removeMediator= function (mediatorName)
{
	var mediator= this.mediatorMap[mediatorName];
	if (mediator)
	{
		var interests= mediator.listNotificationInterests()
		for (var i= 0, n= interests.length; i < n; i++)
		{
			this.removeObserver(interests[i], mediator);
		}
		
		delete this.mediatorMap[mediatorName];
		mediator.onRemove();
	}
	
	return mediator;
};

/**
 * @todo optimize
 * @param {string} mediatorName
 * @return {boolean}
 */
View.prototype.hasMediator= function (mediatorName)
{
	return null != this.mediatorMap[mediatorName];
};

/**
 * @protected
 * @type {Object}
 */
View.prototype.mediatorMap= null;

/**
 * @protected
 * @type {Object}
 */
View.prototype.observerMap= null;

/**
 * @protected
 * @static
 * @type {View}
 */
View.instance= null;

/**
 * @protected
 * @static
 * @const
 * @type {string}
 */
View.SINGLETON_MSG= "View Singleton already constructed!";
